package com.base.core.mvc.ws;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.slf4j.MDC;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import com.base.core.context.utils.LoggerWrapper;
import com.base.core.head.ao.WsMessageAO;
import com.base.core.head.constants.CodeResVal;
import com.base.core.head.constants.MdcConstants;
import com.base.core.head.vo.WsMessageListVO;
import com.base.core.head.vo.WsMessageObjectVO;
import com.base.core.head.vo.WsMessagePageVO;
import com.base.core.head.vo.WsMessageVO;
import com.base.core.mvc.web.MethodParameterValid;
import com.gitee.magic.core.converter.AbstractConverterEditor;
import com.gitee.magic.core.exception.ApplicationException;
import com.gitee.magic.framework.base.constant.Config;
import com.gitee.magic.framework.base.constant.Message;
import com.gitee.magic.framework.base.rest.BusinessRestException;
import com.gitee.magic.framework.head.constants.BaseCode;
import com.gitee.magic.framework.head.exception.BusinessAdviceException;
import com.gitee.magic.framework.head.exception.BusinessException;

/**
 * @author start
 */
@Component
public class WsMessageTemplate {

	@Value("${base.skip.deprecatedapi:false}")
	private Boolean skipDeprecatedApi;
	@Autowired
	private List<WsBaseMessage> messages;
	
	@Deprecated
	public void onMessage(WsMessageAO<String> ao,BiConsumer<String,Object> ws) {
		MDC.put(MdcConstants.USERID, String.valueOf(ao.getUserId()));
		MDC.put(MdcConstants.CLIENTID, ao.getClientId());
		for(WsBaseMessage msg:messages) {
			if(!msg.support(ao.getBizType())) {
				continue;
			}
			Class<?> prototype=AopProxyUtils.ultimateTargetClass(msg);
			if(!prototype.isAnnotationPresent(Service.class)) {
				continue;
			}
			try {
				msg.afterPropertiesSet(ao.getUserId());
				for(Method method:prototype.getMethods()) {
					if(method.getName().equalsIgnoreCase(ao.getMsgType())) {
						Object[] params=new Object[method.getParameterCount()];
						for(int i=0;i<method.getParameterCount();i++) {
							Class<?> cls=method.getParameterTypes()[i];
							if(cls.equals(BiConsumer.class)) {
								params[i]=ws;
							}else if(cls.equals(WsMessageAO.class)) {
								Type genericTypes=method.getGenericParameterTypes()[i];
								if (genericTypes instanceof ParameterizedType) {
					                ParameterizedType pt = (ParameterizedType) genericTypes;
					                Type[] typeArgs = pt.getActualTypeArguments();
					                if(typeArgs[0].equals(String.class)) {
					                	params[i]=ao;
					                }else {
					                	AbstractConverterEditor<?> editor = MethodParameterValid.getRequestBodyEditor(ao.getData(),
					                			typeArgs[0]);
					                	WsMessageAO<Object> wm=new WsMessageAO<>();
					                	BeanUtils.copyProperties(ao, wm);
					                	wm.setData(editor.getValue());
					                	params[i]=wm;
					                }
					            }
							}else {
								for(Annotation an:method.getParameterAnnotations()[i]) {
									if(an.annotationType().equals(RequestBody.class)) {
										AbstractConverterEditor<?> editor = MethodParameterValid.getRequestBodyEditor(ao.getData(),
												method.getGenericParameterTypes()[i]);
										params[i]= editor.getValue();
									}
								}
							}
						}
						try {
							Object res=method.invoke(msg,params);
							if(res!=null) {
								if(res.getClass().equals(WsMessageVO.class)) {
									ws.accept(ao.getMsgType(),null);
								}else if(res.getClass().equals(WsMessageObjectVO.class)) {
									WsMessageObjectVO<?> v=(WsMessageObjectVO<?>)res;
									ws.accept(ao.getMsgType(),v.getData());
								}else if(res.getClass().equals(WsMessageListVO.class)) {
									WsMessageListVO<?> v=(WsMessageListVO<?>)res;
									ws.accept(ao.getMsgType(),v.getData());
								}else if(res.getClass().equals(WsMessagePageVO.class)) {
									WsMessagePageVO<?> v=(WsMessagePageVO<?>)res;
									ws.accept(ao.getMsgType(),v.getData());
								}else {
									ws.accept(ao.getMsgType(),res);
								}
							}
						} catch (IllegalAccessException e) {
							throw new ApplicationException(e);
						} catch (IllegalArgumentException e) {
							throw new ApplicationException(e);
						} catch (InvocationTargetException e) {
							if(e.getCause()!=null) {
								LoggerWrapper.print(e.getCause());
								if(e.getCause().getClass().equals(BusinessException.class)) {
									ws.accept("error", Message.parse(e.getCause().getMessage()));
								}else {
									ws.accept("error", e.getCause().getMessage());
								}
							}else {
								throw new ApplicationException(e);
							}
						}
					}
				}
			}finally {
				msg.destory(ao.getUserId());
			}
		}
	}
	
	public void onMessage(WsMessageAO<String> ao,Consumer<Object> ws) {
		MDC.put(MdcConstants.USERID, String.valueOf(ao.getUserId()));
		MDC.put(MdcConstants.CLIENTID, ao.getClientId());
		for(WsBaseMessage msg:messages) {
			if(!msg.support(ao.getBizType())) {
				continue;
			}
			Class<?> prototype=AopProxyUtils.ultimateTargetClass(msg);
			if(!prototype.isAnnotationPresent(Service.class)) {
				continue;
			}
			try {
				msg.afterPropertiesSet(ao.getUserId());
				for(Method method:prototype.getMethods()) {
					Boolean isMsgType=method.getName().equals(ao.getMsgType());
					if(method.isAnnotationPresent(PostMapping.class)) {
						PostMapping pm=method.getAnnotation(PostMapping.class);
						for(String v:pm.value()) {
							if(v.equals(ao.getMsgType())) {
								isMsgType=true;
								break;
							}
						}
					}
					if(isMsgType) {
						if(Config.isTestActive()) {
		                	if(!skipDeprecatedApi) {
		                		if(method.isAnnotationPresent(Deprecated.class)) {
		                            Message message=Message.parse(CodeResVal.CODE_10000);
									ws.accept(ao.ofState(message.getCode(), message.getMessage()));
									return;
		                        }
		                	}
		                }
						Object[] params=new Object[method.getParameterCount()];
						for(int i=0;i<method.getParameterCount();i++) {
							Class<?> cls=method.getParameterTypes()[i];
							if(cls.equals(Consumer.class)) {
								params[i]=ws;
							}else if(cls.equals(WsMessageAO.class)) {
								Type genericTypes=method.getGenericParameterTypes()[i];
								if (genericTypes instanceof ParameterizedType) {
					                ParameterizedType pt = (ParameterizedType) genericTypes;
					                Type[] typeArgs = pt.getActualTypeArguments();
					                if(typeArgs[0].equals(String.class)) {
					                	params[i]=ao;
					                }else {
					                	AbstractConverterEditor<?> editor = MethodParameterValid.getRequestBodyEditor(ao.getData(),
					                			typeArgs[0]);
					                	WsMessageAO<Object> wm=new WsMessageAO<>();
					                	BeanUtils.copyProperties(ao, wm);
					                	wm.setData(editor.getValue());
					                	params[i]=wm;
					                }
					            }
							}else {
								for(Annotation an:method.getParameterAnnotations()[i]) {
									if(an.annotationType().equals(RequestBody.class)) {
										AbstractConverterEditor<?> editor = MethodParameterValid.getRequestBodyEditor(ao.getData(),
												method.getGenericParameterTypes()[i]);
										params[i]= editor.getValue();
									}
								}
							}
						}
						try {
							Object res=method.invoke(msg,params);
							if(res!=null) {
								ws.accept(res);
							}
						} catch (IllegalAccessException e) {
							throw new ApplicationException(e);
						} catch (IllegalArgumentException e) {
							throw new ApplicationException(e);
						} catch (InvocationTargetException e) {
							if(e.getCause()!=null) {
								LoggerWrapper.print(e.getCause());
								if(e.getCause().getClass().equals(BusinessException.class)) {
									BusinessException ex=(BusinessException)e.getCause();
									Message message=Message.parse(ex.getMessage());
									if(ex.getArgs()!=null) {
										message.setMessage(String.format(message.getMessage(), ex.getArgs()));
									}
									ws.accept(ao.ofState(message.getCode(), message.getMessage()));
								}else if(e.getCause().getClass().equals(BusinessAdviceException.class)) {
									BusinessAdviceException ex=(BusinessAdviceException)e.getCause();
									Message message=Message.parse(ex.getMessage());
									if(ex.getArgs()!=null) {
										message.setMessage(String.format(message.getMessage(), ex.getArgs()));
									}
									ws.accept(ao.ofState(message.getCode(), message.getMessage()));
								}else if(e.getCause().getClass().equals(BusinessRestException.class)) {
									BusinessRestException ex=(BusinessRestException)e.getCause();
									ws.accept(ao.ofState(ex.getCode(), ex.getMessage()));
								}else {
									Message message=Message.parse(BaseCode.CODE_500);
									ws.accept(ao.ofState(message.getCode(), message.getMessage()));
								}
							}else {
								throw new ApplicationException(e);
							}
						}
					}
				}
			}finally {
				msg.destory(ao.getUserId());
			}
		}
	}
	

}
